package edu.cicese.alz;

import edu.cicese.alz.swing.SwingUtil;
import edu.cicese.alz.swing.TableCellListener;
import edu.cicese.alz.swing.dialogs.ConfirmationDialog;
import edu.cicese.alz.swing.dialogs.ErrorDialog;
import edu.cicese.alz.swing.dialogs.InputDialog;
import edu.cicese.alz.swing.tables.ButtonColumn;
import edu.cicese.alz.swing.tables.DoubleTableEditor;
import edu.cicese.alz.swing.tables.IntegerTableEditor;
import edu.cicese.alz.swing.tables.SubjectsTable;
import edu.cicese.alz.util.PropertyUtil;
import edu.cicese.alz.util.Utilities;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.Box;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.io.File;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;

/**
 * Created by: Eduardo Quintana Contreras
 * Date: 29/05/12
 * Time: 11:53 AM
 */
public class FrameSubjects extends SwingComponent {
	private Map<Integer, int[]> idxChanges = new HashMap();
	private TreeSet<Integer> idxNewSubjects = new TreeSet<>();
	private Map<Integer, Subject> idxSubjects = new HashMap();

	private SubjectsTable tblSubject = new SubjectsTable(idxChanges, idxNewSubjects);
	private DefaultTableModel dtmSubjects;
	private Object[] tbIdentifiers = new Object[]{"ID", "Name", "Age", "Home Latitude", "Home Longitude", ""};
	private JComboBox<String> cmbCriteria;
	private JTextField txtSearch;
	private JScrollPane spSubject;

	private FrameCoordinates frmCoordinates;

	//TODO Get first usable ID or get last + 1?
	private int lastUserID = -1;

	private JButton btnSave, btnCancel;

	private final int INSERT = 1;
	public static final int UPDATE = 0;
	public static final int DELETE = -1;
	private final int MODIFIED = 0;
	private final int NOT_MODIFIED = 1;

	private Subject tmpSubject = new Subject();

	public FrameSubjects() {
		super(620, 430, "Subjects");

		initComponents();

//		fillSubjectsDemo();
		fillSubjects(Query.getSubjects());
	}

	private void initComponents() {
		spSubject = new JScrollPane(tblSubject);
		dtmSubjects = new DefaultTableModel();

		Action action = new AbstractAction() {
			public void actionPerformed(ActionEvent e) {
				TableCellListener tcl = (TableCellListener) e.getSource();
				rowSelectionChanged(tcl.getRow());
				int selectedRow = tblSubject.getSelectedRow();
				tmpSubject.setName(dtmSubjects.getValueAt(selectedRow, 1).toString());
				tmpSubject.setAge(dtmSubjects.getValueAt(selectedRow, 2).toString());
				tmpSubject.setLat(dtmSubjects.getValueAt(selectedRow, 3).toString());
				tmpSubject.setLon(dtmSubjects.getValueAt(selectedRow, 4).toString());
			}
		};
		new TableCellListener(tblSubject, action);

		dtmSubjects.setColumnIdentifiers(tbIdentifiers);
		tblSubject.setModel(dtmSubjects);
		tblSubject.setRowHeight(20);
		tblSubject.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
			@Override
			public void valueChanged(ListSelectionEvent e) {
				if (!e.getValueIsAdjusting() && tblSubject.getSelectedRow() != -1) {
					int selectedRow = tblSubject.getSelectedRow();
					tmpSubject.setName(dtmSubjects.getValueAt(selectedRow, 1).toString());
					tmpSubject.setAge(dtmSubjects.getValueAt(selectedRow, 2).toString());
					tmpSubject.setLat(dtmSubjects.getValueAt(selectedRow, 3).toString());
					tmpSubject.setLon(dtmSubjects.getValueAt(selectedRow, 4).toString());
				}
			}
		});
//		tblSubject.setGridColor(new Color(212, 214, 217));


		int[] minWidths = new int[]{60, 250, 40, 110, 120, 25};
		int[] maxWidths = new int[]{120, Integer.MAX_VALUE, 120, 110, 120, 25};
		for (int c = 0; c < minWidths.length; c++) {
			TableColumn column = tblSubject.getColumnModel().getColumn(c);
			column.setPreferredWidth(minWidths[c]);
			column.setMaxWidth(maxWidths[c]);
			column.setResizable(false);
		}

		Action actionSelectCoordinates = new AbstractAction() {
			public void actionPerformed(ActionEvent e) {
				int row = Integer.valueOf(e.getActionCommand());
				selectCoordinates(row);
			}
		};

		ButtonColumn buttonColumn = new ButtonColumn(tblSubject, actionSelectCoordinates, 5);
		buttonColumn.setMnemonic(KeyEvent.VK_D);

		tblSubject.getColumnModel().getColumn(2).setCellEditor(new IntegerTableEditor());
		tblSubject.getColumnModel().getColumn(3).setCellEditor(new DoubleTableEditor());
		tblSubject.getColumnModel().getColumn(4).setCellEditor(new DoubleTableEditor());

		tblSubject.setGridColor(new Color(212, 214, 217));
		tblSubject.getTableHeader().setReorderingAllowed(false);
		tblSubject.getTableHeader().setBackground(SwingUtil.cTableHeader);
		tblSubject.getTableHeader().setFont(SwingUtil.fontTableHeader);


		JButton btnNew = createCustomButton("", "New subject", "b_new");
		btnNew.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				actionNew();
			}
		});

		JButton btnNewBatch = createCustomButton("", "New subject's batch", "b_batch");
		btnNewBatch.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				actionNewBatch();
			}
		});

		JButton btnDelete = createCustomButton("", "Delete selected subject(s)", "b_delete");
		btnDelete.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				actionDelete();
			}
		});

		btnSave = createCustomButton("", "Save changes", "b_save");
		btnSave.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				actionSave();
			}
		});

		btnCancel = createCustomButton("", "Cancel changes", "b_cancel");
		btnCancel.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				actionCancel();
			}
		});

		Box boxButtons = Box.createHorizontalBox();
		boxButtons.add(btnNew);
		boxButtons.add(btnNewBatch);
		boxButtons.add(btnDelete);
		boxButtons.add(btnSave);
		boxButtons.add(btnCancel);
		boxButtons.add(Box.createHorizontalGlue());


		txtSearch = SwingUtil.createTextField(150, 24);
		txtSearch.setFont(new Font("Calibri", Font.PLAIN, 14));

		cmbCriteria = SwingUtil.createComboBox(new Object[]{"Name", "User ID"}, 70, 24);

		JButton btnSearch = createButton("Search", "Search subject(s)", "search", 66, 24);
		btnSearch.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				actionSearch();
			}
		});

		JButton btnShowAll = createButton("Show all", "Show all subjects", "show_all", 76, 24);
		btnShowAll.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				actionShowAll();
			}
		});

		Box boxSearch = Box.createHorizontalBox();
		boxSearch.add(Box.createHorizontalGlue());
		boxSearch.add(txtSearch);
		boxSearch.add(Box.createHorizontalStrut(5));
		boxSearch.add(cmbCriteria);
		boxSearch.add(Box.createHorizontalStrut(5));
		boxSearch.add(btnSearch);
		boxSearch.add(Box.createHorizontalStrut(5));
		boxSearch.add(btnShowAll);
		boxSearch.add(Box.createHorizontalGlue());


		JPanel pContent = new JPanel(new GridBagLayout());
		pContent.setBackground(SwingUtil.cBackground);
		GridBagConstraints gbc = new GridBagConstraints();

		// Row 1
		gbc.insets = new Insets(0, 0, 0, 0);
		gbc.gridx = 0;
		gbc.gridy = 0;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		pContent.add(boxButtons, gbc);

		// Row 2
		gbc.insets = new Insets(5, 5, 0, 5);
		gbc.gridx = 0;
		gbc.gridy = 1;
		pContent.add(boxSearch, gbc);

		// Row 3
		gbc.fill = GridBagConstraints.BOTH;
		gbc.insets = new Insets(5, 5, 5, 5);
		gbc.gridx = 0;
		gbc.gridy = 2;
		gbc.weightx = 0.5;
		gbc.weighty = 0.5;
		gbc.anchor = GridBagConstraints.FIRST_LINE_START;
		pContent.add(spSubject, gbc);

		setContentPane(pContent);


		// Set shortcuts
		InputMap inputMap = tblSubject.getInputMap(JTable.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
		ActionMap actionMap = tblSubject.getActionMap();

		Action actionNew = new AbstractAction() {
			public void actionPerformed(ActionEvent e) {
				addRow();
			}
		};

		Action actionDelete = new AbstractAction() {
			public void actionPerformed(ActionEvent e) {
				deleteRow();
			}
		};

		Action actionUndelete = new AbstractAction() {
			public void actionPerformed(ActionEvent e) {
				undeleteRow();
			}
		};

		inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.CTRL_DOWN_MASK), "new");
		actionMap.put("new", actionNew);
		inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "delete");
		actionMap.put("delete", actionDelete);
		inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, 0), "undelete");
		actionMap.put("undelete", actionUndelete);

		enableButtons();
	}


	private int getLastUserID() {
		int lastUserID = -1;
		try {
			ResultSet resultSet = Query.executeQuery(Query.queryLastUserID());
			if (resultSet != null) {
				if (resultSet.next()) {
					lastUserID = resultSet.getInt(1);
				}
			}
			else {
				Query.showConnectionError(this);
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			Query.close();
		}

		return lastUserID;
	}

	private void fillSubjects(ResultSet resultSet) {
		idxSubjects.clear();
		try {
			if (resultSet != null) {
				while (resultSet.next()) {
					int userID = resultSet.getInt(1);
					String userName = resultSet.getString(2);
					String age = getNotNullValue(resultSet.getString(3));
					String latitude = getNotNullValue(resultSet.getString(4));
					String longitude = getNotNullValue(resultSet.getString(5));
					idxSubjects.put(userID, new Subject(userID, userName, age, latitude, longitude));
					dtmSubjects.addRow(new Object[]{userID, userName, age, latitude, longitude});
				}
			} else {
				Query.showConnectionError(this);
			}
			packScrollPane();
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			Query.close();
		}

		lastUserID = getLastUserID();
	}

	private void fillSubjectsDemo() {
		idxSubjects.clear();
		for (int userID = 1; userID < 31; userID++) {
			String name = "anon " + userID;
			idxSubjects.put(userID, new Subject(userID, name, "60", "31.86", "-100.0"));
			dtmSubjects.addRow(new Object[]{userID, name, "60", "31.86", "-100.0"});
		}
	}

	private void actionSearch() {
		String txt = txtSearch.getText().trim();
		if (!txt.equals("")) {
			if (isChanged()) {
				showSaveChangesDialog();
			}
			if (cmbCriteria.getSelectedIndex() == 0) {
				dtmSubjects.setNumRows(0);
				fillSubjects(Query.getSubjects(txtSearch.getText().trim()));
			} else {
				try {
					int userID = Integer.parseInt(txt);
					dtmSubjects.setNumRows(0);
					fillSubjects(Query.getSubjects(userID));
				} catch (NumberFormatException nfe) {
					new ErrorDialog(this, "Input error", "Please enter an integer value.");
//					JOptionPane.showMessageDialog(null, "Please enter an Integer value", "Data Input Error", JOptionPane.ERROR_MESSAGE);
				}
			}
		} else {
			new ErrorDialog(this, "Input error", "Enter name o user ID.");
//			System.out.println("Enter name o user ID.");
		}
	}

	private void actionShowAll() {
		dtmSubjects.setNumRows(0);
		fillSubjects(Query.getSubjects());
	}

	private void actionNew() {
		addRow();
	}

	private void actionNewBatch() {
		addRows();
	}

	private void actionDelete() {
		deleteRow();
	}

	private void actionSave() {
		saveChanges();
	}

	private void actionCancel() {
		cancelChanges();
	}

	// Separate the changes by type
	private void saveChanges() {
		if (isHomeDirOK()) {
			List<Object[]> inserted = new ArrayList<>();
			List<Object[]> updated = new ArrayList<>();
			List<Integer> deleted = new ArrayList<>();
			List<Integer> rowsToDelete = new ArrayList<>();

			Object[] tmp;
			int[] types;

			for (int rowChanged : idxChanges.keySet()) {
				types = idxChanges.get(rowChanged);
				tmp = new Object[dtmSubjects.getColumnCount()];
				for (int c = 0; c < dtmSubjects.getColumnCount(); c++) {
					tmp[c] = dtmSubjects.getValueAt(rowChanged, c);
				}
//				tmp[tmp.length - 2] = 0; // table count
//				tmp[tmp.length - 1] = rowChanged; // row

				switch (types[0]) {
					case INSERT:
						inserted.add(tmp);
//						addUpdateSubjectIndex(tmp);
						break;
					case DELETE:
						deleted.add((Integer) tmp[0]);
						rowsToDelete.add(rowChanged);
//						deleteSubjectIndex((Integer) tmp[0]);
						break;
					case UPDATE:
						updated.add(tmp);
//						addUpdateSubjectIndex(tmp);
						break;
				}
			}

			// show confirmation dialog if deleting
			if (deleted.isEmpty() || showDeleteConfirmationDialog()) {
				for (Object[] object : inserted) {
					addUpdateSubjectIndex(object);
				}

				for (Object[] object : updated) {
					addUpdateSubjectIndex(object);
				}

				for (Integer object : deleted) {
					deleteSubjectIndex(object);
				}

				updateChanges(inserted, deleted, updated);

				deleteSubjectFromTable(rowsToDelete);

				clearChanges();
				packScrollPane();
			}
		}
	}

	private boolean isHomeDirOK() {
		String homeDir = PropertyUtil.getProperty(PropertyUtil.HOME_DIR);
		if (homeDir != null && !homeDir.equals("") && new File(homeDir).exists()) {
			return true;
		} else {
			System.err.println("Select a valid home dir first! [Settings -> Home directory]");
			return false;
		}
	}

	// Update database and the folder structure
	private void updateChanges(List<Object[]> inserted, List<Integer> deleted, List<Object[]> updated) {
		if (!deleted.isEmpty()) {
			System.out.println(Query.queryDeleteSubject(deleted));
			if (!Query.executeUpdate(Query.queryDeleteSubject(deleted))) {
				Query.showConnectionError(this);
			}
			else {
				deleteSubjectFolders(deleted);
				String dropper = Query.queryDropAccelerometerTables(deleted);
				if (!dropper.equals("")) {
					System.out.println("DROP -> " + dropper);
					if (!Query.executeUpdate(Query.queryDropAccelerometerTables(deleted))) {
						Query.showConnectionError(this);
					}
				}
			}
		}
		if (!inserted.isEmpty()) {
			System.out.println(Query.queryInsertSubject(inserted));
			if (!Query.executeUpdate(Query.queryInsertSubject(inserted))) {
				Query.showConnectionError(this);
			}
			else {
				createSubjectFolders(inserted);
			}
		}
		for (Object[] subject : updated) {
			System.out.println(Query.queryUpdateSubject(subject));
			if (!Query.executeUpdate(Query.queryUpdateSubject(subject))) {
				Query.showConnectionError(this);
			}
		}
	}

	private void deleteSubjectFolders(List<Integer> subjects) {
		String usersDir = PropertyUtil.getProperty(PropertyUtil.HOME_DIR) + Utilities.getSystemSeparator() + "users" + Utilities.getSystemSeparator();
		for (Integer subjectID : subjects) {
			Utilities.deleteDirectory(usersDir + subjectID);
		}
	}

	private void deleteSubjectFromTable(List<Integer> rowsToDelete) {
//		List<Integer> rowsToDelete = new ArrayList<>();
//		for (Integer subjectID : subjects) {
//			rowsToDelete.add((Integer) subjectID[subjectID.length - 1]);
//			rowsToDelete.add(subjectID);
//		}
		Collections.sort(rowsToDelete, Collections.reverseOrder());
		for (int row : rowsToDelete) {
			dtmSubjects.removeRow(row);
		}
	}

	private void createSubjectFolders(List<Object[]> subjects) {
		String systemSeparator = Utilities.getSystemSeparator();
		String usersDir = PropertyUtil.getProperty(PropertyUtil.HOME_DIR) + systemSeparator + "users" + systemSeparator;
		String userDir;
		for (Object[] subject : subjects) {
			userDir = usersDir + subject[0];
			Utilities.deleteDirectory(userDir);
			new File(userDir + systemSeparator + "infiles").mkdirs();
			new File(userDir + systemSeparator + "charts").mkdirs();
			new File(userDir + systemSeparator + "json").mkdirs();
		}
	}

	// Checks if the name or age of the row's subject was modified
	private void rowSelectionChanged(int row) {
		System.out.println("checking");
		// if there was a change (checking the temporary values saved when the row was selected)
		if (!tmpSubject.equals(
				dtmSubjects.getValueAt(row, 1).toString().trim(),
				dtmSubjects.getValueAt(row, 2).toString(),
				dtmSubjects.getValueAt(row, 3).toString(),
				dtmSubjects.getValueAt(row, 4).toString())) {
			// if the content returned to the initial state
			if (returnedInitialState(row)) {
				// if the row was schedule for 'UPDATE', delete this entry from the changes index
				//TODO Check idx null pointer?
				if (idxChanges.get(row)[0] == UPDATE) {
					idxChanges.remove(row);
				}
				// if the row was schedule for 'DELETE', change the content state to 'NOT_MODIFIED'
				else {
					idxChanges.put(row, new int[]{idxChanges.get(row)[0], NOT_MODIFIED});
				}
			} else {
				addChangedRow(row, UPDATE);
			}
			System.out.println("change at " + row);
//			System.out.print(tmpRow[0].toString().trim() + " vs " + dtmSubjects.getValueAt(row, 1).toString().trim());
//			System.out.println(" & " + tmpRow[1] + " vs " + getAge(dtmSubjects.getValueAt(row, 2)) + "   ROW: " + row);
		}

		enableButtons();
	}

	private void addRow() {
		if (!tblSubject.isEditing()) {
			addSubject();
			packScrollPane();
		}
	}

	private void addRows() {
		if (!tblSubject.isEditing()) {
			InputDialog dialog = new InputDialog(this, "Input");
			if (dialog.isOk()) {
				int quantity = dialog.getQuantity();
				if (quantity != -1) {
					for (int i = 1; i <= quantity; i++) {
						addSubject();
					}
				}
			}
			dialog.dispose();
			packScrollPane();
		}
	}

	private void addSubject() {
		dtmSubjects.addRow(new Object[]{++lastUserID, "anonymous", "", "", ""});
		tblSubject.changeSelection(dtmSubjects.getRowCount() - 1, 0, false, false);
		addChangedRow(tblSubject.getSelectedRow(), INSERT);
		idxNewSubjects.add(tblSubject.getSelectedRow());
	}

	private void deleteRow() {
		if (!tblSubject.isEditing()) {
			for (int row : tblSubject.getSelectedRows()) {
				addChangedRow(row, DELETE);
			}
		}
	}

	private void undeleteRow() {
		if (!tblSubject.isEditing()) {
			int[] types;
			for (int row : tblSubject.getSelectedRows()) {

				if (idxChanges.containsKey(row)) {
					types = idxChanges.get(row);

					if (types[0] == DELETE) {

						if (idxNewSubjects.contains(row)) {
							idxChanges.put(row, new int[]{INSERT, NOT_MODIFIED}); //MODIFIED or NOT_MODIFIED, doesn't matter
						} else if (types[1] == MODIFIED) {
							idxChanges.put(row, new int[]{UPDATE, MODIFIED});
						} else {
							idxChanges.remove(row);
						}
					}
				}
			}
		}

		enableButtons();
	}

	private void addChangedRow(int row, int type) {
		if (idxChanges.containsKey(row)) {
			if (type == UPDATE) {
				idxChanges.put(row, new int[]{idxChanges.get(row)[0], MODIFIED});
			} else {
				idxChanges.put(row, new int[]{type, idxChanges.get(row)[1]});
			}
		} else {
			idxChanges.put(row, new int[]{type, type == UPDATE ? MODIFIED : NOT_MODIFIED});
		}
		enableButtons();
	}

	private boolean returnedInitialState(int row) {
		int userID = (Integer) dtmSubjects.getValueAt(row, 0);
		if (idxSubjects.containsKey(userID)) {
			Subject subject = idxSubjects.get(userID);
			return subject.equals(
					dtmSubjects.getValueAt(row, 1).toString().trim(),
					dtmSubjects.getValueAt(row, 2).toString(),
					dtmSubjects.getValueAt(row, 3).toString(),
					dtmSubjects.getValueAt(row, 4).toString());
		}
		return false;
	}

	private void showSaveChangesDialog() {
		ConfirmationDialog dialog = new ConfirmationDialog(this,
				"Save changes",
				"Do you want to save the changes before continuing?");
		if (dialog.isOk()) {
			saveChanges();
		} else {
			cancelChanges();
		}
		dialog.dispose();

//		if (JOptionPane.showConfirmDialog(this,
//				"Do you want to save the changes before continuing?",
//				"Save changes", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE,
//				new ImageIcon(getClass().getClassLoader().getResource("icons/save.png")))
//				== JOptionPane.YES_OPTION) {
//			saveChanges();
//		} else {
//			cancelChanges();
//		}
	}

	private boolean showDeleteConfirmationDialog() {
		ConfirmationDialog dialog = new ConfirmationDialog(this,
				"Confirmation dialog",
				"There are deletions scheduled. All data related to these subjects will be permanently removed from the database. Are you sure?");
		boolean okd = dialog.isOk();
		dialog.dispose();
		return okd;
//		return (JOptionPane.showConfirmDialog(this,
//				"There are deletions scheduled  \nAll data related to these subjects will be permanently removed from the database\nAre you sure?",
//				"Confirmation", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE,
//				new ImageIcon(getClass().getClassLoader().getResource("icons/warning.png")))
//				== JOptionPane.YES_OPTION);
	}

	private boolean showCancelConfirmationDialog() {
		ConfirmationDialog dialog = new ConfirmationDialog(this,
				"Confirmation dialog",
				"All unsaved data will be lost. Continue?");
		boolean okd = dialog.isOk();
		dialog.dispose();
		return okd;
//		return (JOptionPane.showConfirmDialog(this,
//				"All unsaved data will be lost\nContinue?",
//				"Confirmation", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE,
//				new ImageIcon(getClass().getClassLoader().getResource("icons/warning.png")))
//				== JOptionPane.YES_OPTION);
	}

	private void cancelChanges() {
		if (showCancelConfirmationDialog()) {
			ArrayList<Integer> rowsToDelete = new ArrayList<>();
			for (int newSubject : idxNewSubjects.descendingSet()) {
				rowsToDelete.add(newSubject);
			}
			Collections.sort(rowsToDelete);
			if (!rowsToDelete.isEmpty()) {
				dtmSubjects.setNumRows(rowsToDelete.get(0));
			}
			int row;
			int userID;
			for (Map.Entry<Integer, int[]> change : idxChanges.entrySet()) {
				if (change.getValue()[1] == MODIFIED) {
					row = change.getKey();
					userID = (Integer) dtmSubjects.getValueAt(row, 0);
					dtmSubjects.setValueAt(idxSubjects.get(userID).getName(), row, 1);
					dtmSubjects.setValueAt(idxSubjects.get(userID).getAge(), row, 2);
					dtmSubjects.setValueAt(idxSubjects.get(userID).getLat(), row, 3);
					dtmSubjects.setValueAt(idxSubjects.get(userID).getLon(), row, 4);
				}
			}
			clearChanges();
		}
	}

	private void addUpdateSubjectIndex(Object[] subject) {
		idxSubjects.put((Integer) subject[0], new Subject(
				(Integer) subject[0],
				subject[1].toString(),
				subject[2].toString(),
				subject[3].toString(),
				subject[4].toString()));
	}

	private void deleteSubjectIndex(int userID) {
		idxSubjects.remove(userID);
	}

	private void clearChanges() {
		idxChanges.clear();
		idxNewSubjects.clear();
		tblSubject.repaint();
		enableButtons();
	}

	private boolean isChanged() {
		return !idxChanges.isEmpty();
	}

	private void enableButtons() {
		btnSave.setEnabled(isChanged());
		btnCancel.setEnabled(isChanged());
	}

	private void packScrollPane() {
		int subjects = dtmSubjects.getRowCount() <= 15 ? dtmSubjects.getRowCount() : 15;
		spSubject.setPreferredSize(new Dimension(400, (subjects * 20) + 24));
		revalidate();
//		repaint();
	}

	private String getNotNullValue(Object value) {
		return value != null ? value.toString() : "";
	}

	private void selectCoordinates(int row) {
		String latValue = dtmSubjects.getValueAt(row, 3).toString();
		String lonValue = dtmSubjects.getValueAt(row, 4).toString();

		setEnabled(false);

		if (frmCoordinates != null) {
			frmCoordinates.deselect();
			frmCoordinates.setVisible(true);
		} else {
			frmCoordinates = new FrameCoordinates(this);
		}

		if (!latValue.equals("") && !lonValue.equals("")) {
			frmCoordinates.setCoordinates(Double.parseDouble(latValue), Double.parseDouble(lonValue));
		}
	}

	public void setCoordinates(double lat, double lon) {
		dtmSubjects.setValueAt(lat, tblSubject.getSelectedRow(), 3);
		dtmSubjects.setValueAt(lon, tblSubject.getSelectedRow(), 4);
	}

	public static void main(String[] a) {
		SwingUtil.setUIManager();
		new FrameSubjects();
	}
}
